/*
 * /linux-3.0.8/drivers/media/video/gt2440_ov9650.c
 */
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/media.h>
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-mediabus.h>
#include "gt2440_ov9650.h"

/* help data structure */
struct gt2440_ov9650_reg gt2440_ov9650_regs[] = {
	//{ REG_COM7, 0x80 },	/* SCCB reset, output format  */
	{ REG_COM2, 0x10 },	/* Set soft sleep mode */
	{ REG_OFCON, 0x43 },	/* Power down register  */
	{ REG_ACOM, 0x12 },	/* reserved  */
	{ REG_ADC, 0x91 },	/* reserved  */
	{ REG_COM5, 0x00 },	/* System clock options  */
	//{ REG_MVFP, 0x0c },	/* Mirror / vflip  */
	//{ REG_BLUE, 0x80 },	/* blue gain  */
	//{ REG_RED, 0x80 },	/* red gain  */
	//{ REG_GAIN, 0x00 },	/* Gain lower 8 bits (rest in vref)  */
	//{ REG_AEC, 0xf0 },	/* More bits of AEC value  */
	//{ REG_COM1, 0x00 },	/* Control 1  */
	{ REG_COM3, 0x04 },	/* Output byte swap, signals reassign  */
	{ REG_COM4, 0x80 },	/* Vario Pixels   */
	{ REG_CLKRC, 0x83 },	/* Clock control, try to run at 24Mhz  */
	{ REG_COM7, 0x04 },	/* SCCB reset, output format: VGA */
	{ REG_COM2, 0x01 },	/* Output drive, soft sleep mode */
	{ REG_COM9, 0x2e },	/*  Control 9  - gain ceiling  */
	{ REG_COM10, 0x00 },	/* Slave mode, HREF vs HSYNC, signals negate */
	{ REG_HSTART, 0x1d + 8 }, /*  Horiz start high bits  */
	{ REG_HSTOP, 0xbd + 8 }, /*  Horiz stop high bits  */
	{ REG_HREF, 0xbf },	/* HREF pieces  */

	{ REG_VSTART, 0x00 },	/* Vert start high bits  */
	{ REG_VSTOP, 0x80 },	/* Vert stop high bits  */
	{ REG_VREF, 0x12 },	/* Pieces of GAIN, VSTART, VSTOP  */
	{ REG_EDGE, 0xa6 },	/* Edge enhancement factor  */
	{ REG_COM16, 0x02 },	/* Color matrix coeff double option  */
	{ REG_COM17, 0x08 },	/* Single Frame out, Banding filter  */
	{ REG_PSHFT, 0x00 },	/* Pixel delay after HREF  */
	{ 0x16, 0x06 },
	{ REG_CHLF, 0xc0 },	/* reserved  */
	{ 0x34, 0xbf },
	{ 0xa8, 0x80 },
	{ 0x96, 0x04 },
	{ REG_TSLB, 0x00 },	/* YUYV format  */
	{ 0x8e, 0x00 },
	{ REG_COM12, 0x77 },	/* HREF option, UV average  */
	{ 0x8b, 0x06 },
	{ 0x35, 0x91 },
	{ 0x94, 0x88 },
	{ 0x95, 0x88 },
	{ REG_COM15, 0xc1 },	/* Output range, RGB 555 vs 565 option */
	{ REG_GRCOM, 0x3f },	/* Analog BLC & regulator */
	{ REG_COM6, 0x42 | 1 }, /* HREF & ADBLC options */
	{ REG_COM8, 0xe5 }, 	/* AGC/AEC options */
	{ REG_COM13, 0x90 },	/* Gamma selection, Color matrix enable, UV delay */
	{ REG_HV, 0x80 },	/*  Manual banding filter MSB  */
	{ 0x5c, 0x96 },		/* reserved up to 0xa5 */
	{ 0x5d, 0x96 },
	{ 0x5e, 0x10 },
	{ 0x59, 0xeb },
	{ 0x5a, 0x9c },
	{ 0x5b, 0x55 },
	{ 0x43, 0xf0 },
	{ 0x44, 0x10 },
	{ 0x45, 0x55 },
	{ 0x46, 0x86 },
	{ 0x47, 0x64 },
	{ 0x48, 0x86 },
	{ 0x5f, 0xe0 },
	{ 0x60, 0x8c },
	{ 0x61, 0x20 },
	{ 0xa5, 0xd9 },
	{ 0xa4, 0x74 },		/* reserved  */
	{ REG_COM23, 0x02 },	/*  Color bar test, color gain  */
	{ REG_COM8, (1 << 5) | 0x7 }, /*  AGC/AEC options  */
	{ REG_COM22, 0x23 },	/*  Edge enhancement, denoising  */
	{ REG_COM14, 0x02 },	/*  Edge enhancement options  */
	{ 0xa9, 0xb8 },
	{ 0xaa, 0x92 },
	{ 0xab, 0x0a },
	{ REG_DBLC1, 0xdf },	/*  Digital BLC  */
	{ REG_DBLC_B, 0x00 },	/*  Digital BLC B chan offset  */
	{ REG_DBLC_R, 0x00 },	/*  Digital BLC R chan offset  */
	{ REG_DBLC_GB, 0x00 },	/*  Digital BLC GB chan offset  */
	{ REG_TSLB, 0x00 },	/*  YUYV format  */
	{ REG_AEW, 0x70 },	/*  AGC upper limit  */
	{ REG_AEB, 0x64 },	/*  AGC lower limit  */
	{ REG_VPT, 0xc3 },	/*  AGC/AEC fast mode op region  */
//	{ REG_EXHCH, 0x00 },	/*  Dummy pixel insert MSB  */
//	{ REG_EXHCL, 0x00 },	/*  Dummy pixel insert LSB  */
	{ REG_COM11, (1 << 7) | (3 << 5)}, /*  Night mode, banding filter enable  */
	{ REG_MBD, 0x41 },	/* Manual banding filter value, when COM11[0] is high  */
	{ REG_LCC(5), 0x00 },	/* Lens Correction Option 5  */
	{ REG_COM14, 0x00 },	/* Edge enhancement options  */
	{ REG_EDGE, 0xa4 }	/* Edge enhancement factor  */
};
static const struct gt2440_ov9650_framesize gt2440_ov9650_framesizes[] = {
	{ 640, 480, 0x40 }, /* VGA */
	{ 352, 288, 0x20 }, /* CIF */
	{ 320, 240, 0x10 }, /* QVGA */
	{ 176, 144, 0x08 }, /* QCIF */
};
static const struct gt2440_ov9650_pixelcode gt2440_ov9650_pixelcodes[] = {
	{ V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG },
};

/* data structure */
static const struct v4l2_subdev_core_ops gt2440_ov9650_core_ops = {
	.s_power = gt2440_ov9650_s_power,
	.log_status = gt2440_ov9650_log_status,
};
static const struct v4l2_subdev_video_ops gt2440_ov9650_video_ops = {
	.s_stream = gt2440_ov9650_s_stream,
};
static const struct v4l2_subdev_pad_ops gt2440_ov9650_pad_ops = {
	.enum_mbus_code = gt2440_ov9650_enum_mbus_code,
	.enum_frame_size = gt2440_ov9650_enum_frame_size,
	.get_fmt = gt2440_ov9650_get_fmt,
	.set_fmt = gt2440_ov9650_set_fmt,
};
static const struct v4l2_subdev_ops gt2440_ov9650_subdev_ops = {
	.core = &gt2440_ov9650_core_ops,
	.video = &gt2440_ov9650_video_ops,
	.pad = &gt2440_ov9650_pad_ops,
};
static const struct v4l2_subdev_internal_ops gt2440_ov9650_internal_ops = {
	.registered = gt2440_ov9650_registered,
	.open = gt2440_ov9650_open,
};
static const struct v4l2_ctrl_ops gt2440_ov9650_ctrl_ops = {
	.s_ctrl	= gt2440_ov9650_s_ctrl,
};


/***** help function *****/

/***** help function *****/
static void __gt2440_ov9650_set_power(struct gt2440_ov9650_info *info, int on)
{
	struct gt2440_ov9650_platdata *pdata = info->pdata;
	pdata->set_power(pdata, on);
	info->streaming = 0;
}
static int gt2440_ov9650_write_array(struct i2c_client *client,
			      const struct gt2440_ov9650_reg *regs,
			      unsigned int len)
{
	int i;
	for (i = 0; i < len; i++) {
		int ret = gt2440_ov9650_write_reg(client, regs[i].addr, regs[i].value);
		if (ret < 0)
			return ret;
	}
	return 0;
}
static int gt2440_ov9650_set_gamma_curve(struct gt2440_ov9650_info *info)
{
	struct i2c_client *client = v4l2_get_subdevdata(&info->v4l2_sub);
	static const u8 gamma_curve[] = {
		/* Values taken from OV application note. */
		0x40, 0x30, 0x4b, 0x60, 0x70, 0x70, 0x70, 0x70,
		0x60, 0x60, 0x50, 0x48, 0x3a, 0x2e, 0x28, 0x22,
		0x04, 0x07, 0x10, 0x28,	0x36, 0x44, 0x52, 0x60,
		0x6c, 0x78, 0x8c, 0x9e, 0xbb, 0xd2, 0xe6
	};
	u8 addr = REG_GSP;
	unsigned int i;
	int ret = 0;
	for (i = 0; i < ARRAY_SIZE(gamma_curve) && !ret; i++) {
		ret = gt2440_ov9650_write_reg(client, addr, gamma_curve[i]);
		addr++;
	}
	return ret;
};
static int gt2440_ov9650_set_color_matrix(struct gt2440_ov9650_info *info)
{
	struct i2c_client *client = v4l2_get_subdevdata(&info->v4l2_sub);
	static const u8 mtx[] = {
		/* MTX1..MTX9, MTXS */
		0x3a, 0x3d, 0x03, 0x12, 0x26, 0x38, 0x40, 0x40, 0x40, 0x0d
	};
	u8 addr = REG_MTX(1);
	unsigned int i;
	for (i = 0; i < ARRAY_SIZE(mtx); i++) {
		int ret = gt2440_ov9650_write_reg(client, addr, mtx[i]);
		if (ret < 0)
			return ret;;
		addr++;
	}
	return 0;
}
static int __gt2440_ov9650_set_params(struct gt2440_ov9650_info *info)
{
	struct i2c_client *client = v4l2_get_subdevdata(&info->v4l2_sub);
	int ret;
	ret = gt2440_ov9650_write_array(client, gt2440_ov9650_regs,
				 ARRAY_SIZE(gt2440_ov9650_regs));
	if (ret < 0)
		return ret;
	ret = gt2440_ov9650_set_gamma_curve(info);
	if (ret < 0)
		return ret;
	return gt2440_ov9650_set_color_matrix(info);
}

static int gt2440_ov9650_set_framesize(struct v4l2_mbus_framefmt *framefmt,
				   const struct gt2440_ov9650_framesize **framesizes)
{	
	unsigned int min_err = ~0;
	int i = ARRAY_SIZE(gt2440_ov9650_framesizes);
	const struct gt2440_ov9650_framesize *framesize = &gt2440_ov9650_framesizes[0],
		*match = NULL;
	while (i--) {
		int err = abs(framesize->width - framefmt->width)
				+ abs(framesize->height - framefmt->height);
		if (err < min_err) {
			min_err = err;
			match = framesize;
		}
		framesize++;
	}
	if (match) {
		framefmt->width  = match->width;
		framefmt->height = match->height;
		if (framesizes)
			*framesizes = match;
		return 0;
	}
	return -EINVAL;
}
static void gt2440_ov9650_set_format(struct gt2440_ov9650_info *info,
			      struct v4l2_mbus_framefmt *framefmt)
{	
	const struct gt2440_ov9650_framesize *framesize = NULL;
	unsigned int index;

	gt2440_ov9650_set_framesize(framefmt, &framesize);

	if (framesize)
		info->com7_reg = framesize->com7_reg;
	index = 0; /* ov965x_get_pixfmt_index(ov965x, mf); */
	framefmt->colorspace	= V4L2_COLORSPACE_JPEG;
	framefmt->code	= gt2440_ov9650_pixelcodes[index].pixelcode;
	framefmt->field	= V4L2_FIELD_NONE;
}


/***** v4l2_subdev_ops function *****/
static int gt2440_ov9650_s_power(struct v4l2_subdev *v4l2_sub, int on)
{	dprintk("gt2440_ov9650_s_power()\n");
	struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);;
	mutex_lock(&info->lock);
	if (info->power == !on) {
		__gt2440_ov9650_set_power(info, on);
		info->apply_fmt = 1;
	}
	info->power += on ? 1 : -1;
	WARN_ON(info->power < 0);
	mutex_unlock(&info->lock);
	return 0;
}
static int gt2440_ov9650_log_status(struct v4l2_subdev *v4l2_sub)
{	dprintk("gt2440_ov9650_log_status()\n");
	v4l2_ctrl_handler_log_status(v4l2_sub->ctrl_handler, v4l2_sub->name);
	return 0;
}
static int gt2440_ov9650_s_stream(struct v4l2_subdev *v4l2_sub, int on)
{	dprintk("gt2440_ov9650_s_stream()\n");
	struct i2c_client *client = v4l2_get_subdevdata(v4l2_sub);
		struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);;
	int ret = 0;
	mutex_lock(&info->lock);
	if (info->streaming == !on) {
		if (on) {
			ret = __gt2440_ov9650_set_params(info);
			if (!ret && info->apply_fmt) {
				ret = gt2440_ov9650_write_reg(client, REG_COM7,
						   info->com7_reg);
				gt2440_ov9650_write_reg(client, REG_COM2, 0x01);
				/* ov965x->apply_fmt = 0; */
			}
		} else {
			ret = gt2440_ov9650_write_reg(client, REG_COM2, 0x11);
		}
	}
	if (!ret)
		info->streaming += on ? 1 : -1;
	WARN_ON(info->streaming < 0);
	mutex_unlock(&info->lock);
	return ret;
}
static int gt2440_ov9650_enum_mbus_code(struct v4l2_subdev *v4l2_sub,
				 struct v4l2_subdev_fh *fh,
				 struct v4l2_subdev_mbus_code_enum *code_enum)
{	dprintk("gt2440_ov9650_enum_mbus_code()\n");
	if (code_enum->index >= ARRAY_SIZE(gt2440_ov9650_pixelcodes))
		return -EINVAL;
	code_enum->code = gt2440_ov9650_pixelcodes[code_enum->index].pixelcode;
	return 0;
}
static int gt2440_ov9650_enum_frame_size(struct v4l2_subdev *v4l2_sub,
				   struct v4l2_subdev_fh *fh,
				   struct v4l2_subdev_frame_size_enum *frame_size_enum)
{	dprintk("gt2440_ov9650_enum_frame_size()\n");
	int i = ARRAY_SIZE(gt2440_ov9650_pixelcodes);
	if (frame_size_enum->index > ARRAY_SIZE(gt2440_ov9650_pixelcodes))
		return -EINVAL;
	while (--i)
		if (frame_size_enum->code == gt2440_ov9650_pixelcodes[i].pixelcode) break;

	frame_size_enum->code = gt2440_ov9650_pixelcodes[i].pixelcode;
	frame_size_enum->min_width  = gt2440_ov9650_framesizes[frame_size_enum->index].width;
	frame_size_enum->max_width  = frame_size_enum->min_width;
	frame_size_enum->max_height = gt2440_ov9650_framesizes[frame_size_enum->index].height;
	frame_size_enum->min_height = frame_size_enum->max_height;
	return 0;
}
static int gt2440_ov9650_get_fmt(struct v4l2_subdev *v4l2_sub, struct v4l2_subdev_fh *fh,
			  struct v4l2_subdev_format *format)
{	dprintk("gt2440_ov9650_get_fmt()\n");
	struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);;
	struct v4l2_mbus_framefmt *framefmt;
	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
		framefmt = v4l2_subdev_get_try_format(fh, 0);
		format->format = *framefmt;
		return 0;
	}
	mutex_lock(&info->lock);
	format->format = info->framefmt;
	mutex_unlock(&info->lock);
	return 0;
}
static int gt2440_ov9650_set_fmt(struct v4l2_subdev *v4l2_sub, struct v4l2_subdev_fh *fh,
			  struct v4l2_subdev_format *format )
{	dprintk("gt2440_ov9650_set_fmt()\n");
	struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);;
	struct v4l2_mbus_framefmt *framefmt = NULL;
	int ret = 0;

	gt2440_ov9650_set_format(info, &format ->format);
	mutex_lock(&info->lock);
	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
		if (fh != NULL) {
			framefmt = v4l2_subdev_get_try_format(fh, format->pad);
			*framefmt = format->format;
		}
	} else {
		if (info->streaming) {
			ret = -EBUSY;
		} else {
			info->framefmt = format->format;
			info->apply_fmt = 1;
		}
	}
	mutex_unlock(&info->lock);
	return ret;
}


/***** v4l2_subdev_internal_ops function *****/
static int gt2440_ov9650_registered(struct v4l2_subdev *v4l2_sub)
{	dprintk("gt2440_ov9650_registered()\n");
	struct i2c_client *client = v4l2_get_subdevdata(v4l2_sub);
	struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);
	u8 pid, ver;
	int ret;

	mutex_lock(&info->lock);
	__gt2440_ov9650_set_power(info, 1);
    usleep_range(25000, 26000);
	ret = gt2440_ov9650_read_reg(client, REG_PID, &pid);
	if (!ret)
		ret = gt2440_ov9650_read_reg(client, REG_VER, &ver);
	if (!ret) {
		if (pid == OV965X_PID_MSB &&
		    (ver == OV9650_PID_LSB || ver == OV9652_PID_LSB))
			v4l2_info(v4l2_sub, "success to find OV96%02X Sensor\n", ver);
		else
			v4l2_err(v4l2_sub, "failed to find OV9650 Sensor: 0x%02x, 0x%02x\n", pid, ver);
	}
	__gt2440_ov9650_set_power(info, 0);
	mutex_unlock(&info->lock);
	return ret;
}
static int gt2440_ov9650_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *subdev_fh)
{	dprintk("gt2440_ov9650_open()\n");
	struct v4l2_mbus_framefmt *framefmt = v4l2_subdev_get_try_format(subdev_fh, 0);
	
	framefmt->width = gt2440_ov9650_framesizes[0].width;
	framefmt->height = gt2440_ov9650_framesizes[0].height;
	framefmt->colorspace = gt2440_ov9650_pixelcodes[0].colorspace;
	framefmt->code = gt2440_ov9650_pixelcodes[0].pixelcode;
	framefmt->field = V4L2_FIELD_NONE;
	v4l2_info(subdev, "%s:%d\n", __func__, __LINE__);
	return 0;
}
static int gt2440_ov9650_s_ctrl(struct v4l2_ctrl *ctrl)
{	dprintk("gt2440_ov9650_s_ctrl()\n");
	struct v4l2_subdev *v4l2_sub = 
		&container_of(ctrl->handler, struct gt2440_ov9650_info, ctrl.handler)->v4l2_sub;
	struct gt2440_ov9650_info *info = 
		container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);
	struct i2c_client *client = v4l2_get_subdevdata(v4l2_sub);
	int ret = 0;
	mutex_lock(&info->lock);
	if (info->power == 0)
		goto unlock;
	switch (ctrl->id) {
	case V4L2_CID_AUTO_WHITE_BALANCE:
		break;
	case V4L2_CID_BRIGHTNESS:
		/* ret = gt2440_ov9650_write_reg(client, REG_USER_BRIGHTNESS, ctrl->val); */
		break;
	case V4L2_CID_CONTRAST:
		/* ret = gt2440_ov9650_write_reg(client, REG_USER_CONTRAST, ctrl->val); */
		break;
	case V4L2_CID_HFLIP:
		break;
	case V4L2_CID_EXPOSURE_AUTO:
		break;
	case V4L2_CID_POWER_LINE_FREQUENCY:
		ret = -EINVAL; //s5k6aa_set_anti_banding(ov965x, ctrl->val);
		break;
	case V4L2_CID_SATURATION:
		/* ret = gt2440_ov9650_write_reg(client, REG_USER_SATURATION, ctrl->val); */
		break;
	}
unlock:
	mutex_unlock(&info->lock);
	return ret;
}


/***** probe and remove function *****/
static int gt2440_ov9650_probe(struct i2c_client *client, const struct i2c_device_id *id)
{	dprintk("gt2440_ov9650_probe():\n");
	dprintk("i2c_client: name = %s, addr = 0x%2x\n", client->name, client->addr);
	dprintk("i2c_device_id: name = %s \n", id->name);
	struct gt2440_ov9650_platdata *pdata;
	struct gt2440_ov9650_info *info;
	int ret;
	int i;
	pdata = (struct gt2440_ov9650_platdata *)client->dev.platform_data;
	if (pdata == NULL) {
		dev_err(&client->dev,"failed to get platform data for ov9650\n");
		return -EINVAL;
	}
	info = kzalloc(sizeof(struct gt2440_ov9650_info), GFP_KERNEL);
	if (info == NULL){
		dev_err(&client->dev,"failed to allocate struct gt2440_ov9650_info{}\n");
		return -ENOMEM;
	}
	info->dev = &client->dev;
	info->pdata = pdata;
	info->mclk_frequency = pdata->mclk_frequency;
	info->com7_reg = gt2440_ov9650_framesizes[0].com7_reg;
	mutex_init(&info->lock);

	if(pdata->init_gpio){
		ret = pdata->init_gpio(pdata);	
		if(ret){
			dev_err(info->dev,"failed to init gpio\n");
			goto exit_1;
		}
	}else{
		dev_err(info->dev,"failed to get gpio\n");
		goto exit_1;
	}

	info->framefmt.width = gt2440_ov9650_framesizes[0].width;
	info->framefmt.height = gt2440_ov9650_framesizes[0].height;
	info->framefmt.colorspace = gt2440_ov9650_pixelcodes[0].colorspace;
	info->framefmt.code = gt2440_ov9650_pixelcodes[0].pixelcode;
	info->framefmt.field = V4L2_FIELD_NONE;
	info->v4l2_sub.internal_ops = &gt2440_ov9650_internal_ops;
	info->v4l2_sub.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	v4l2_i2c_subdev_init(&info->v4l2_sub, client, &gt2440_ov9650_subdev_ops);
	ret = v4l2_ctrl_handler_init(&info->ctrl.handler, 16);
	if (ret){
		dev_err(info->dev,"failed to init v4l2_ctrl_handler\n");
		goto exit_2;
	}
	info->ctrl.auto_wb = v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops,
				V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
	info->ctrl.auto_exp = v4l2_ctrl_new_std_menu(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops,
				V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 
				0, V4L2_EXPOSURE_AUTO);
	info->ctrl.exposure = v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops, 
		V4L2_CID_EXPOSURE, 0, 6000000U, 1, 100000U);
	info->ctrl.hflip = v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops, 
							V4L2_CID_HFLIP, 0, 1, 1, 0);
	info->ctrl.vflip = v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops, 
								V4L2_CID_VFLIP, 0, 1, 1, 0);
	v4l2_ctrl_cluster(2, &info->ctrl.hflip);
	v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops,
					V4L2_CID_SATURATION, -127, 127, 1, 0);
	v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops, 
					V4L2_CID_BRIGHTNESS, -127, 127, 1, 0);
	v4l2_ctrl_new_std(&info->ctrl.handler, &gt2440_ov9650_ctrl_ops, 
					V4L2_CID_CONTRAST, -127, 127, 1, 0);
	if (info->ctrl.handler.error) {
		dev_err(info->dev,"failed to set v4l2_ctrl_handler: error = %d\n", 
			info->ctrl.handler.error);
		ret = info->ctrl.handler.error;
		goto exit_3;
	}
	info->v4l2_sub.ctrl_handler = &info->ctrl.handler;
	return 0;
	
exit_3:
	v4l2_ctrl_handler_free( &info->ctrl.handler);
	info->v4l2_sub.ctrl_handler = NULL;
exit_2:
	if(pdata->exit_gpio)
		ret = pdata->exit_gpio(pdata);
exit_1:
	kfree(info);
	i2c_set_clientdata(client, NULL);
	return ret;
}
static int gt2440_ov9650_remove(struct i2c_client *client)
{	dprintk("gt2440_ov9650_remove():\n");
	dprintk("i2c_client: name = %s, i2c_client: addr = 0x%2x\n", 
		client->name, client->addr);
	struct v4l2_subdev *v4l2_sub = i2c_get_clientdata(client);
	struct gt2440_ov9650_info *info;
	struct gt2440_ov9650_platdata*pdata;
	int i, ret;
	if(v4l2_sub == NULL)
		return 0;
	info = container_of(v4l2_sub, struct gt2440_ov9650_info, v4l2_sub);
	pdata = info->pdata;
	v4l2_ctrl_handler_free( &info->ctrl.handler);
	info->v4l2_sub.ctrl_handler = NULL;
	v4l2_set_subdevdata(v4l2_sub, NULL);

	if(pdata->exit_gpio)
		ret = pdata->exit_gpio(pdata);
	kfree(info);
	i2c_set_clientdata(client, NULL);
	return 0;
}
/***** gt2440 board probe and remove function *****/
static const struct i2c_device_id gt2440_ov9650_id_table[] = {
	{ "OV9650", 0 },
	{ "OV9652", 0 },
};
static struct i2c_driver gt2440_ov9650_driver = {
	.driver = {
		.name = "gt2440-ov9650",
		.owner = THIS_MODULE,
	},
	.probe = gt2440_ov9650_probe,
	.remove = gt2440_ov9650_remove,
	.id_table = gt2440_ov9650_id_table, 
};
static int __init gt2440_ov9650_init(void)
{	dprintk("gt2440_ov9650_init()");
	return i2c_add_driver(&gt2440_ov9650_driver);
}
static void __exit gt2440_ov9650_exit(void)
{	dprintk("gt2440_ov9650_exit()");
	i2c_del_driver(&gt2440_ov9650_driver);
}
module_init(gt2440_ov9650_init);
module_exit(gt2440_ov9650_exit);
MODULE_DESCRIPTION("GT2440 OV9650 Camera Sensor Device Driver");
MODULE_AUTHOR("Liguang13579<1659890447@qq.com>");
MODULE_LICENSE("GPL v2");
